﻿using System;
using CI.Misc;
using NUnit.Framework;
using CI.Interfaces;
using CI.Organism;
using Rhino.Mocks;

// ReSharper disable InconsistentNaming
namespace Mesopotamia.UnitTests.Neural
{
    [TestFixture]
    public class SimpleNeuralSystemTests
    {
        SimpleNeuralSystem _sns;
        MockRepository _mocks;
        Neuron _neuron1;
        Neuron _neuron2;
        CI.Organism.Organism _organism;
        private readonly IHostingEnvironment _hostingEnvironment = new CI.Mesopotamia.MSRBHost(new MockHostingEnvironmentPhysical(), new CI.Mesopotamia.ScenarioLocalXML());

        [SetUp]
        public void Setup()
        {
            _mocks = new MockRepository();
            _hostingEnvironment.Organisms = new OrganismCollection();
            _organism = new CI.Organism.Organism(_hostingEnvironment);
            _sns = new SimpleNeuralSystem(_organism);
            _organism.NeuralSystem = _sns;
        }

        #region Checking pre and post conditions of neural system
        /// <summary>
        /// This checks and makes sure when the simple neural network system is setup that the lists are set clean
        /// </summary>
        [Test]
        public void CtorNotNullLists()
        {
            // check and make sure they are not null
            Assert.IsNotNull(_sns.ExecutingNeuralList, "Executing Neural List is not null");
            Assert.IsNotNull(_sns.WaitingNeuralList, "Waiting Nerual list is not null");
        }

        /// <summary>
        /// We expect that the waiting list is not null before evaluation
        /// </summary>
        [Test, ExpectedException(typeof(SimpleNeuralSystem.NerualSystemException), ExpectedMessage = "WaitingNeuralList can not be null")]
        public void EvaluateNullWaitingNeuralListException()
        {
            // clear the waiting list
            _sns.WaitingNeuralList = null;
            _sns.Evaluate();
        }

        /// <summary>
        /// We expect that after Evaluation the executing list is not the same as the waiting list
        /// </summary>
        [Test]
        public void EvaluatePostWaitingExecutingNotEqualFalse()
        {
            _sns.Evaluate();
            Assert.AreNotSame(_sns.WaitingNeuralList, _sns.ExecutingNeuralList, "Waiting list should not be the same as executing list, should be new one");
        }

        /// <summary>
        /// We want to check to make sure once it starts executing it creates a new waiting list and would be empty in this scenario when it returns
        /// </summary>
        [Test, Ignore("check later")]
        public void EvaluatePostWaitingEmptyTrue()
        {
            _sns.Evaluate();
            //Assert.IsEmpty((ICollection<INeuron>)_sns.WaitingNeuralList, "Waiting list should be empty");
        }
        #endregion

        #region Checking behavior of simple neural system, mock out the neurons and make sure they get called

        /// <summary>
        /// Cycle through the list executing the neurons
        /// </summary>
        [Test]
        public void EvaluateTrue()
        {
            // Mock a neuron
            _neuron1 = _mocks.DynamicMock<Neuron>(1, _sns, Guid.NewGuid());
            _neuron2 = _mocks.DynamicMock<Neuron>(1, _sns, Guid.NewGuid());

            // add it to the waiting list
            _sns.AddToWaitingList(_neuron1);
            _sns.AddToWaitingList(_neuron2);

            // this is what we expect to be called in a normal evaluation
            // e.g. all the neurons we added the be called
            using (_mocks.Record())
            {
                Expect.Call(_neuron1.ActivationFunction(1)).Return(true);
                Expect.Call(_neuron2.ActivationFunction(1)).Return(true);
            }

            // run a heart beat
            using (_mocks.Playback())
            {
                _sns.Evaluate();
            }

            // verify it got called
            _mocks.VerifyAll();

        }

        /// <summary>
        /// The purpose of this test it so make sure when a heart beat is fired with a bound energy synapse
        /// the two connecting neurons in this case get added to the waitinglist
        /// </summary>
        [Test, Ignore("Not implemented")]
        public void HeartbeatEnergyNeuronActivatedInWaitingListTrue()
        {


            // bind an Organism energy to the energy synapse

            // trigger heartbeat

            // check the waiting list to make sure their are two neurons that we expect are in their

        }

        /// <summary>
        /// This test check to make sure if a synapse has a output hold value x it does cause the neuron
        /// to be checked over x number of heartbeats
        /// </summary>
        [Test]
        public void Evaluate_HeartbeatOutputHold_True()
        {
            var fireCount = 0;
            Neuron neuron = new StepNeuron(10, _sns, Guid.NewGuid());

            var outputSynapseIO = new SynapseIO("bindingName", "shortName", "description", Guid.NewGuid());
            outputSynapseIO.ActivationEvent += o => fireCount++;

            // add a input synapse with a hold value
            var inputSynapse = new SimpleSynapse(Guid.NewGuid(), _sns);
            inputSynapse.OutputHold = 2;
            neuron.InputSynapse.Add(inputSynapse);
            inputSynapse.AddSynapticConnectionTarget(neuron);


            // add output synapse
            var outputSynapse = new SimpleSynapse(Guid.NewGuid(), _sns);
            neuron.OutputSynapse.Add(outputSynapse);
            outputSynapse.AddSynapticConnectionSource(neuron);
            outputSynapse.AddSynapticConnectionTarget(outputSynapseIO);

            // add a neuron
            _sns.AddNeuron(neuron);

            // fire the input synapse
            inputSynapse.FireInput(100);
            
            _organism.NeuralSystem.Evaluate();
            Assert.AreEqual(3, fireCount, "Output not fired 3");

            _organism.NeuralSystem.Evaluate();
            Assert.AreEqual(3, fireCount, "Output should have not fired 4");
        }

        /// <summary>
        /// add two input synapses to a neuron, one strong one weak
        /// fire them both and the neuron for x iterations and the 
        /// weak one should get stronger and be able to fire on its ownn
        /// </summary>
        [Test]
        public void Evaluate_FireTogetherWireTogether_Equal()
        {
            bool fired = false;
            Neuron neuron = new StepNeuron(10, _sns, Guid.NewGuid());

            var strongSynapse = new SimpleSynapse(Guid.NewGuid(), _sns);
            strongSynapse.InitInputWeight = 1;
            var strongSynapseLearningRule = new UseItOrLoseIt(0.1, 0.01, strongSynapse, Guid.NewGuid());
            strongSynapse.LearningRule = strongSynapseLearningRule;

            var weakSynapse = new SimpleSynapse(Guid.NewGuid(), _sns);
            weakSynapse.InitInputWeight = 1;
            var weakSynapseLearningRule = new UseItOrLoseIt(0.1, 0.01, weakSynapse, Guid.NewGuid());
            weakSynapse.LearningRule = weakSynapseLearningRule;

            var outputSynapse = new SimpleSynapse(Guid.NewGuid(), _sns);
            outputSynapse.ActivationEvent += o => fired = true;

            strongSynapse.AddSynapticConnectionTarget(neuron);
            weakSynapse.AddSynapticConnectionTarget(neuron);
            outputSynapse.AddSynapticConnectionSource(neuron);

            neuron.InputSynapse.Add(strongSynapse);
            neuron.InputSynapse.Add(weakSynapse);
            neuron.OutputSynapse.Add(outputSynapse);

            _sns.AddNeuron(neuron);

            FireSynapsesAndCheck(strongSynapse, weakSynapse, true, ref fired);
            FireSynapsesAndCheck(strongSynapse, weakSynapse, false, ref fired);

        }

        private void FireSynapsesAndCheck(SimpleSynapse strongSynapse, SimpleSynapse weakSynapse, bool fireStrong, ref bool fired)
        {
            for (int i = 0; i < 10; i++)
            {
                fired = false;

                // fire the input synapses
                if (fireStrong)
                    strongSynapse.FireInput(5);
                weakSynapse.FireInput(5);

                // fire the neural and check response
                _organism.HeartbeatCount++;
                _organism.NeuralSystem.Evaluate();
                Assert.True(fired, "Output not fired");
            }
        }

        #endregion
    }
}


